import { type App } from 'vue'
import type { NavigationGuardNext, RouteMeta } from 'vue-router'
import { getCache, setCache } from '../cache'
import { BaseType, login } from '../index'
import { rbacConfig } from './config'
import PermissionTemplate from './permission.vue'
import RoleTemplate from './role.vue'


const PERMISSIONS_KEY = 'rbac:permissions'
const ROLES_KEY = 'rbac:roles'
const IS_INIT_RBAC_KEY = 'rbac:isInitRbac'

let permissionCache: Array<BaseType> | undefined = undefined
let rolesCache: Array<BaseType> | undefined = undefined

export const listPermissions = (): Array<BaseType> => {
  if (permissionCache) {
    return permissionCache
  } else {
    permissionCache = getCache<Array<BaseType>>(PERMISSIONS_KEY, [])
    return permissionCache
  }
}

export const listRoles = (): Array<BaseType> => {
  if (rolesCache) {
    return rolesCache
  } else {
    rolesCache = getCache<Array<BaseType>>(ROLES_KEY, [])
    return rolesCache
  }
}

const isInit = () => getCache(IS_INIT_RBAC_KEY, false)

export const initRbac = (permissions?: any[], roles?: any[]): void => {
  setCache(PERMISSIONS_KEY, permissions || [])
  setCache(ROLES_KEY, roles || [])
  setCache(IS_INIT_RBAC_KEY, true)
}

export const hasRbac = (meta: RouteMeta) => {
  let rb
  if (rbacConfig.role) {
    if (meta.roles && meta.roles.length) {
      rb = hasAnyRoles(meta.roles)
    } else {
      rb = true
    }
  } else {
    rb = true
  }

  let pb
  if (rbacConfig.permission) {
    if (meta.permissions && meta.permissions.length) {
      pb = hasAnyPermissions(meta.permissions)
    } else {
      pb = true
    }
  } else {
    pb = true
  }
  return rb && pb
}

const minRoleCheck = (roles: Array<BaseType>): boolean => (rbacConfig.minRole ? roles.includes(rbacConfig.minRole) : true)

export const serverRbacCheck = async (meta: RouteMeta, next: NavigationGuardNext): Promise<boolean> => {
  if (rbacConfig.permission || rbacConfig.role) {
    if (rbacConfig.root && login.userInfo.isRoot) {
      return true
    }
    if (isInit()) {
      if (hasRbac(meta)) {
        return true
      } else {
        await next('/403')
        return false
      }
    } else {
      const { permissions, roles } = await rbacConfig.current(rbacConfig.role, rbacConfig.permission)
      if (!minRoleCheck(roles)) {
        await next('/403?role=' + rbacConfig.minRole)
        return false
      } else {
        initRbac(permissions, roles)
        return true
      }
    }
  } else {
    return true
  }
}

const hasAll = (cache: Array<BaseType>, values: Array<BaseType>): boolean => {
  if (rbacConfig.root && login.userInfo.isRoot) {
    return true
  }

  if (values.length === 0) {
    return false
  }

  for (const value of values) {
    if (!cache.includes(value)) {
      return false
    }
  }
  return true
}

const hasAny = (cache: Array<BaseType>, values: Array<BaseType>): boolean => {
  if (rbacConfig.root && login.userInfo.isRoot) {
    return true
  }

  if (values.length === 0) {
    return false
  }

  for (const value of values) {
    if (cache.includes(value)) {
      return true
    }
  }
  return false
}

export const hasAllRoles = (values: Array<BaseType>): boolean => hasAll(listRoles(), values)
export const hasAnyRoles = (values: Array<BaseType>): boolean => hasAny(listRoles(), values)
export const hasAllPermissions = (values: Array<BaseType>): boolean => hasAll(listPermissions(), values)
export const hasAnyPermissions = (values: Array<BaseType>): boolean => hasAny(listPermissions(), values)

const removeNode = (el: any): void => {
  if (el.parentNode) {
    el.parentNode.removeChild(el)
  } else {
    el.innerHTML = ''
    el.classList.add('hidden')
    el.style.display = 'none'
  }
}

export default {
  install: (app: App, options: any = {}): void => {
    const prefix = options?.componentPrefix ?? ''
    app.component(prefix + 'Role', RoleTemplate)
    app.component(prefix + 'Permission', PermissionTemplate)
    app.directive('role', {
      mounted(el, { value, modifiers }): void {
        if (modifiers.or) {
          hasAnyRoles(value) || removeNode(el)
        } else {
          hasAllRoles(value) || removeNode(el)
        }
      },
    })
    app.directive('permission', {
      mounted(el, { value, modifiers }): void {
        if (modifiers.or) {
          hasAnyPermissions(value) || removeNode(el)
        } else {
          hasAllPermissions(value) || removeNode(el)
        }
      },
    })
  },
}
